/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intel.diceros.provider.symmetric.util; import com.intel.diceros.crypto.BlockCipher; import com.intel.diceros.crypto.DataLengthException; import com.intel.diceros.crypto.InvalidCipherTextException; import com.intel.diceros.crypto.OutputLengthException; import com.intel.diceros.crypto.modes.CBCBlockCipher; import com.intel.diceros.crypto.modes.CTRBlockCipher; import com.intel.diceros.crypto.modes.XTSBlockCipher; import com.intel.diceros.crypto.params.CipherParameters; import com.intel.diceros.crypto.params.KeyParameter; import com.intel.diceros.crypto.params.ParametersWithIV; import com.intel.diceros.crypto.spec.SupportedSpecImpl; import com.intel.diceros.crypto.spec.SupportedSpecSpi; import com.intel.diceros.provider.DicerosProvider; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.security.*; import java.security.spec.AlgorithmParameterSpec; import java.util.Locale; /** * Base Class for BlockCipher. */ public abstract class BaseBlockCipher extends CipherSpi { protected GenericBlockCipher cipher; // wrapping baseEngine, do some preprocessing work protected ParametersWithIV ivParam; // parameter of key data, initialization vector, etc protected int ivLength = -1; // the initialization vector length private SupportedSpecSpi supportedSpec; /** * Constructor * * @param engine the underlying cipher engine, do the actual encryption and * decryption work */ protected BaseBlockCipher(BlockCipher engine) { cipher = new GenericBlockCipherImpl(engine); supportedSpec = new SupportedSpecImpl(); } /** * Constructor * * @param provider provide the the underlying cipher engine which does the actual * encryption and decryption work */ protected BaseBlockCipher(BlockCipherProvider provider) { BlockCipher baseEngine = provider.get(); int modeName = baseEngine.getMode(); if (modeName == Constants.MODE_CTR) { cipher = new GenericBlockCipherImpl(new CTRBlockCipher(baseEngine)); } else if (modeName == Constants.MODE_CBC) { cipher = new GenericBlockCipherImpl(new CBCBlockCipher(baseEngine)); } else if (modeName == Constants.MODE_XTS) { cipher = new GenericBlockCipherImpl(new XTSBlockCipher(baseEngine)); } else { cipher = new GenericBlockCipherImpl(baseEngine); } ivLength = baseEngine.getIVSize(); supportedSpec = new SupportedSpecImpl(); } @Override protected int engineGetBlockSize() { return cipher.getBlockSize(); } @Override protected byte[] engineGetIV() { return (ivParam != null) ? ivParam.getIV() : null; } @Override protected int engineGetKeySize(Key key) { return key.getEncoded().length * 8; } @Override protected int engineGetOutputSize(int inputLen) { if (inputLen < 0) { throw new IllegalArgumentException("Input size must be equal " + "to or greater than zero"); } return cipher.getOutputSize(inputLen); } @Override protected AlgorithmParameters engineGetParameters() { AlgorithmParameters engineParams; String name = cipher.getUnderlyingCipher().getAlgorithmName(); if (name.indexOf('/') >= 0) { name = name.substring(0, name.indexOf('/')); } try { engineParams = AlgorithmParameters.getInstance(name); engineParams.init(getAlgorithmParametersSpec()); } catch (Exception e) { throw new RuntimeException(e.toString()); } return engineParams; } protected AlgorithmParameterSpec getAlgorithmParametersSpec() throws NoSuchAlgorithmException, NoSuchProviderException { byte[] iv = engineGetIV(); if (iv == null) { if (cipher.getUnderlyingCipher().getMode() == Constants.MODE_GCM) { iv = new byte[Constants.GCM_DEFAULT_IV_LEN]; } else { iv = new byte[cipher.getBlockSize()]; } SecureRandom.getInstance("DRNG", "DC").nextBytes(iv); } return new IvParameterSpec(iv); } @Override protected void engineSetMode(String mode) throws NoSuchAlgorithmException { String modeName = mode.toUpperCase(Locale.ENGLISH); if (!modeName.startsWith("CTR") && !modeName.startsWith("CBC") && !modeName.startsWith("XTS") && !modeName.startsWith("GCM")) { throw new NoSuchAlgorithmException("can't support mode " + mode); } } @Override protected void engineSetPadding(String padding) throws NoSuchPaddingException { String paddingName = padding.toUpperCase(Locale.ENGLISH); if (paddingName.equals("NOPADDING") || paddingName.equals("PKCS5PADDING")) { cipher.setPadding(paddingName); } else { throw new NoSuchPaddingException("Padding " + padding + " unknown."); } } @Override protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { if (!(key instanceof SecretKey)) { throw new InvalidKeyException("Key for algorithm " + key.getAlgorithm() + " not suitable for symmetric enryption."); } CipherParameters param = retrieveParam(opmode, key, params, random); try { switch (opmode) { case Cipher.ENCRYPT_MODE: case Cipher.WRAP_MODE: cipher.init(true, param); break; case Cipher.DECRYPT_MODE: case Cipher.UNWRAP_MODE: cipher.init(false, param); break; default: throw new InvalidParameterException("unknown opmode " + opmode + " passed"); } } catch (Exception e) { throw new InvalidKeyException(e.getMessage()); } } protected ParametersWithIV retrieveParam(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { ParametersWithIV param = retrieveParam(key, params); if (ivLength >= 0 && param.getIV() == null) { if ((opmode == Cipher.ENCRYPT_MODE) || (opmode == Cipher.WRAP_MODE)) { SecureRandom ivRandom = random; if (ivRandom == null) { try { ivRandom = SecureRandom.getInstance("DRNG", DicerosProvider.PROVIDER_NAME); } catch (Exception e) { ivRandom = new SecureRandom(); } } byte[] iv = null; if (ivLength == 0 && cipher.getUnderlyingCipher().getMode() == Constants.MODE_GCM) { // default IV size for GCM mode is 96 bit. iv = new byte[Constants.GCM_DEFAULT_IV_LEN]; } else if (ivLength > 0) { iv = new byte[ivLength]; } ivRandom.nextBytes(iv); param.setIV(iv); } else { throw new InvalidAlgorithmParameterException( "no IV set when one expected"); } } return param; } protected ParametersWithIV retrieveParam(Key key, AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException { KeyParameter keyParam = new KeyParameter(key.getEncoded()); byte[] iv = null; if (params == null) { iv = null; } else if (params instanceof IvParameterSpec) { if (ivLength > 0) { iv = ((IvParameterSpec)params).getIV(); if (iv == null || iv.length != ivLength) { throw new InvalidAlgorithmParameterException("IV must be " + ivLength + " bytes long."); } } } else { throw new InvalidAlgorithmParameterException("unknown parameter type."); } ParametersWithIV cipherParam = new ParametersWithIV(keyParam, iv); ivParam = cipherParam; return cipherParam; } @Override protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { AlgorithmParameterSpec paramSpec = null; Class<? extends AlgorithmParameterSpec>[] availableSpecs = supportedSpec.getSupportedSpecs(); if (params != null) { for (int i = 0; i != availableSpecs.length; i++) { try { paramSpec = params.getParameterSpec(availableSpecs[i]); break; } catch (Exception e) { // try another if possible } } if (paramSpec == null) { throw new InvalidAlgorithmParameterException("can't handle parameter " + params.toString()); } } engineInit(opmode, key, paramSpec, random); } @Override protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { try { engineInit(opmode, key, (AlgorithmParameterSpec) null, random); } catch (InvalidAlgorithmParameterException e) { throw new InvalidKeyException(e.getMessage()); } } @Override protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { int length = cipher.getOutputSize(inputLen); if (inputOffset < 0 || inputLen < 0 || (input != null && (inputOffset + inputLen) > input.length)) { throw new IllegalArgumentException( "input offset or input length is nagetive, or input exceeds the array boundary!"); } if (length > 0) { byte[] out = new byte[length]; int len = cipher.processBytes(input, inputOffset, inputLen, out, 0); if (len == 0) { return null; } else if (len != out.length) { byte[] tmp = new byte[len]; System.arraycopy(out, 0, tmp, 0, len); return tmp; } return out; } cipher.processBytes(input, inputOffset, inputLen, null, 0); return null; } @Override protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException { if (inputOffset < 0 || inputLen < 0 || outputOffset < 0 || (input != null && (inputOffset + inputLen) > input.length)) { throw new IllegalArgumentException("input offset or input length or output" + "offset is nagetive, or input exceeds the array boundary!"); } try { return cipher.processBytes(input, inputOffset, inputLen, output, outputOffset); } catch (DataLengthException e) { throw new ShortBufferException(e.getMessage()); } } @Override protected int engineUpdate(ByteBuffer input, ByteBuffer output) throws ShortBufferException { if ((input == null) || (output == null)) { throw new IllegalArgumentException("Buffers must not be null"); } if (input == output) { throw new IllegalArgumentException("Input and output buffers must " + "not be the same object, consider using buffer.duplicate()"); } if (output.isReadOnly()) { throw new ReadOnlyBufferException(); } return cipher.processByteBuffer(input, output); } @Override protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { if (inputOffset < 0 || inputLen < 0 || (input != null && (inputOffset + inputLen) > input.length)) { throw new IllegalArgumentException( "input offset or input length is nagetive, or input exceeds the array boundary!"); } int len = 0; byte[] tmp = new byte[engineGetOutputSize(inputLen)]; if (inputLen != 0) { len = cipher.processBytes(input, inputOffset, inputLen, tmp, 0); } try { len += cipher.doFinal(tmp, len); } catch (DataLengthException e) { throw new IllegalBlockSizeException(e.getMessage()); } catch (InvalidCipherTextException e) { throw new BadPaddingException(e.getMessage()); } if (len == tmp.length) { return tmp; } byte[] out = new byte[len]; System.arraycopy(tmp, 0, out, 0, len); return out; } @Override protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws IllegalBlockSizeException, BadPaddingException, ShortBufferException { if (inputOffset < 0 || inputLen < 0 || outputOffset < 0 || (input != null && (inputOffset + inputLen) > input.length)) { throw new IllegalArgumentException("input offset or input length or output offset" + "is nagetive, or input exceeds the array boundary!"); } try { int len = 0; if (inputLen != 0) { len = cipher.processBytes(input, inputOffset, inputLen, output, outputOffset); } return (len + cipher.doFinal(output, outputOffset + len)); } catch (OutputLengthException e) { throw new ShortBufferException(e.getMessage()); } catch (DataLengthException e) { throw new IllegalBlockSizeException(e.getMessage()); } catch (InvalidCipherTextException e) { throw new BadPaddingException(e.getMessage()); } } @Override protected int engineDoFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { if ((input == null) || (output == null)) { throw new IllegalArgumentException("Buffers must not be null"); } if (input == output) { throw new IllegalArgumentException("Input and output buffers must " + "not be the same object, consider using buffer.duplicate()"); } if (output.isReadOnly()) { throw new ReadOnlyBufferException(); } return cipher.doFinal(input, output); } static public interface GenericBlockCipher { public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException; public String getAlgorithmName(); public BlockCipher getUnderlyingCipher(); public int getOutputSize(int len); public int getBlockSize(); public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException; public int processByteBuffer(ByteBuffer input, ByteBuffer output) throws ShortBufferException; public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException; public int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException; public void setPadding(String padding) throws NoSuchPaddingException; public void updateAAD(byte[] src, int offset, int len); public void updateAAD(ByteBuffer src); public boolean isEncryption(); } private static class GenericBlockCipherImpl implements GenericBlockCipher { private BlockCipher cipher; /** * the Padding type */ private int padding = Constants.PADDING_NOPADDING; /** * are we encrypting or not? */ private boolean forEncryption; /** * index of the content size left in the buffer */ private int buffered = 0; /** * the head length of encryption */ private int head = 0; /** * internal buffer */ private int blockSize = 0; GenericBlockCipherImpl(BlockCipher cipher) { this.cipher = cipher; this.head = cipher.getHeadLength(); } @Override public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException { this.buffered = 0; this.forEncryption = forEncryption; cipher.init(forEncryption, params); blockSize = cipher.getBlockSize(); } @Override public String getAlgorithmName() { return cipher.getAlgorithmName(); } @Override public BlockCipher getUnderlyingCipher() { return cipher; } @Override public int getOutputSize(int len) { if (len == 0 && head == 2) { return 0; } int totalLen = buffered + len; if (padding == Constants.PADDING_NOPADDING) { if (cipher.getMode() != Constants.MODE_GCM) { return totalLen; } else { if (forEncryption) { return cipher.getTagLen() + totalLen; } else { return totalLen - cipher.getTagLen(); } } } if (!forEncryption) return totalLen; if (totalLen < blockSize) return blockSize; return totalLen + blockSize - (len % blockSize) + head; } @Override public int getBlockSize() { return cipher.getBlockSize(); } /** * process an array of bytes, producing output if necessary. * * @param in the input byte array. * @param inOff the offset at which the input data starts. * @param len the number of bytes to be copied out of the input array. * @param out the space for any output that might be produced. * @param outOff the offset from which the output will be copied. * @return the number of output bytes copied to out. * @throws DataLengthException if there isn't enough space in out. * @throws IllegalStateException if the cipher isn't initialised. */ @Override public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException { if (len < 0) { throw new IllegalArgumentException( "Can't have a negative input length!"); } int length = getOutputSize(len); if (length > 0) { if ((((forEncryption && padding == Constants.PADDING_NOPADDING) && (outOff + length) > out.length) || (!forEncryption && (outOff + length - blockSize) > out.length))) { throw new OutputLengthException("output buffer too short"); } } int outConsumed = cipher.processBlock(in, inOff, len, out, outOff); if (cipher.getMode() == Constants.MODE_CBC) { buffered = buffered + len - outConsumed; } return outConsumed; } @Override public int processByteBuffer(ByteBuffer input, ByteBuffer output) throws ShortBufferException { return processByteBuffer(input, output, true); } @Override public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException { try { int length = getOutputSize(0); if (outOff + length > out.length) { throw new OutputLengthException( "output buffer too short for doFinal()"); } return cipher.doFinal(out, outOff); } catch (Exception e) { throw new RuntimeException(e); } finally { buffered = 0; reset(); } } @Override public int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException { int result = 0; try { result = processByteBuffer(input, output, false); } catch (ShortBufferException e) { throw e; } finally { reset(); buffered = 0; } return result; } private int processByteBuffer(ByteBuffer input, ByteBuffer output, boolean isUpdate) throws ShortBufferException { if ((input == null) || (output == null)) { throw new NullPointerException( "Input and output buffers must not be null"); } int inPos = input.position(); int inLimit = input.limit(); int inLen = inLimit - inPos; if (isUpdate && inLen == 0) { return 0; } // input + data unprocessed = 0 int outLenNeeded = getOutputSize(inLen); if (!isUpdate && outLenNeeded == 0) { return 0; } if (!input.isDirect() || !output.isDirect()) { throw new IllegalArgumentException( "ByteBuffer of input and output must be direct"); } if (output.remaining() < outLenNeeded) { throw new ShortBufferException("Need at least " + outLenNeeded + " bytes of space in output buffer"); } // need native process int n = cipher.processByteBuffer(input, output, isUpdate); if (cipher.getMode() == Constants.MODE_CBC) { buffered = buffered + inLen -n; } input.position(input.limit()); output.position(output.position() + n); return n; } /** * Reset the buffer and cipher. After resetting the object is in the same * state as it was after the last init (if there was one). */ public void reset() { cipher.reset(); } public void setPadding(String paddingScheme) throws NoSuchPaddingException { if (paddingScheme == null) { throw new NoSuchPaddingException("null padding"); } else if (paddingScheme.equalsIgnoreCase("NoPadding")) { padding = Constants.PADDING_NOPADDING; } else if (paddingScheme.equalsIgnoreCase("PKCS5Padding")) { padding = Constants.PADDING_PKCS5PADDING; } else { throw new NoSuchPaddingException("Padding: " + paddingScheme + " not implemented"); } if ((padding != Constants.PADDING_NOPADDING) && (cipher.getMode() == Constants.MODE_CTR || cipher.getMode() == Constants.MODE_XTS || cipher.getMode() == Constants.MODE_GCM)) { throw new NoSuchPaddingException(cipher.getAlgorithmName() + " mode must be used with NoPadding"); } cipher.setPadding(padding); } public void updateAAD(byte[] src, int offset, int len) { cipher.updateAAD(src, offset, len); } public void updateAAD(ByteBuffer src) { cipher.updateAAD(src); } public boolean isEncryption() { return this.forEncryption; } } }